#ifndef _THREADPOOL_HPP__
#define _THREADPOOL_HPP__

#include <vector>
#include <queue>
#include <memory>
#include <atomic>
#include <mutex>
#include <condition_variable>
#include <functional>
#include <unordered_map>
#include <thread>
#include <iostream>
#include <future>

const int TASK_QUE_MAX = 2;
const int THREAD_SIZE_MAX = 10;
const int THREAD_IDLE_MAX_TIME = 60; // 单位：秒

// 线程池模式
enum ThreadPoolModel
{
    MODEL_FIXED,   // 固定数量线程
    MODEL_CACHEED, // 线程可动态增长
};

// 线程类
class Thread
{
private:
    using Func = std::function<void(int)>;
    Func func_;
    int thread_id_;
    static int thread_count_num_;

public:
    Thread(Func func)
        : func_(func), thread_id_(thread_count_num_)
    {
        thread_count_num_++;
    }
    ~Thread() = default;
    void start()
    {
        std::thread t(this->func_, thread_id_);
        t.detach();
    }
    int getThreadId() const
    {
        return thread_id_;
    }
};

int Thread::thread_count_num_ = 0;

// 线程池类
class ThreadPool
{
private:
    // std::vector<std::unique_ptr<Thread>> threads_;      //线程数组
    std::unordered_map<int, std::unique_ptr<Thread>> threads_; // 线程哈希
    size_t init_threadpool_size_;                              // 线程初始个数
    std::atomic_int cur_thread_size_;                          // 当前线程数量
    std::atomic_int idle_threads_;                             // 空闲线程数量
    size_t max_thread_size_;                                   // 线程池内最大线程个数
    std::atomic_bool is_runing_;                               // 是否正常执行

    using Task = std::function<void()>; // 函数对象做任务，但是函数的返回值不知道什么类型，这里的void()只是一个中间层。
    std::queue<Task> task_que_;         // 任务队列，父类指针指向子类对象多态。
    std::atomic_uint task_que_size_;    // 任务队列的任务数量，为了线程安全采用原子变量
    size_t task_que_max_;               // 任务队列的上限
    std::mutex task_que_mtx_;           // 任务队列的锁

    std::condition_variable not_full_;  // 表示任务队列不满；
    std::condition_variable not_empty_; // 表示任务队列不空
    std::condition_variable exit_;      // 线程池任务退出通知

    ThreadPoolModel thread_pool_model; // 线程池模式
    std::atomic_bool is_start_;        // 线程池是否启动

public:
    ThreadPool()
        : init_threadpool_size_(0), task_que_size_(0), idle_threads_(0), cur_thread_size_(0), max_thread_size_(THREAD_SIZE_MAX), task_que_max_(TASK_QUE_MAX), is_start_(false), is_runing_(true)
    {
    }
    ~ThreadPool()
    {
        is_runing_ = false;

        // 回收线程池内的线程资源
        // 一种是空闲线程，一种是正在执行任务的线程
        // 当线程池生命周期结束，线程跳出线程函数，线程池删除掉该线程，当线程池的size=0，彻底销毁完成。
        std::unique_lock<std::mutex> lock(task_que_mtx_);
        not_empty_.notify_all();
        exit_.wait(lock, [&]() -> bool
                   { return threads_.size() == 0; });
    }

    ThreadPool(const ThreadPool &) = delete;
    ThreadPool &operator=(const ThreadPool &) = delete;

    // 线程执行函数
    void ThreadFunc(int thread_id)
    {
        auto last_time = std::chrono::high_resolution_clock().now();
        // 一直处理直到将任务队列搬空
        while (true)
        {
            Task task;
            {
                // 获取锁
                std::unique_lock<std::mutex> lock(task_que_mtx_);
                std::cout << "id is : " << std::this_thread::get_id() << "尝试获取任务" << std::endl;
                // 任务列表有任务才会执行
                // 任务列表没有任务的时候，1、就要等待，2、就要在cached模式下，销毁一些空闲线程。
                while (task_que_.size() == 0)
                {
                    if (!is_runing_)
                    {
                        threads_.erase(thread_id);
                        std::cout << "this thread id is " << std::this_thread::get_id() << "exit !" << std::endl;
                        exit_.notify_all();
                        // 退出线程函数，线程销毁。
                        return;
                    }
                    // 判断线程池模式
                    if (this->thread_pool_model == ThreadPoolModel::MODEL_CACHEED)
                    {
                        // 设置1秒钟检测一次，超过的线程就销毁。
                        auto thread_status = not_empty_.wait_for(lock, std::chrono::seconds(1));
                        if (thread_status == std::cv_status::timeout)
                        {
                            auto now_time = std::chrono::high_resolution_clock().now();
                            auto dur_time = std::chrono::duration_cast<std::chrono::seconds>(now_time - last_time).count();
                            if (dur_time > THREAD_IDLE_MAX_TIME && cur_thread_size_ > init_threadpool_size_)
                            {
                                // 进行线程的销毁。
                                // 线程池内该线程的剔除。
                                threads_.erase(thread_id);

                                // 线程池相关成员变量的变化。
                                this->cur_thread_size_--;
                                this->idle_threads_--;
                                std::cout << "this thread id is " << std::this_thread::get_id() << "exit !" << std::endl;
                                // 退出线程函数，线程销毁。
                                return;
                            }
                        }
                    }
                    else
                    {
                        // 等待非空条件.刚开始创建的所有线程，都阻塞在这里等待任务队列里面有任务。
                        not_empty_.wait(lock);
                    }

                    // if (!is_runing_)
                    // {
                    //     threads_.erase(thread_id);

                    //     std::cout << "this thread id is " << std::this_thread::get_id() << "exit !" << std::endl;
                    //     exit_.notify_all();
                    //     // 退出线程函数，线程销毁。
                    //     return;
                    // }
                }

                // 空闲线程减一
                this->idle_threads_--;
                std::cout << "id is : " << std::this_thread::get_id() << "获取任务成功" << std::endl;
                // 从任务队列中取出任务
                task = task_que_.front();
                task_que_.pop();
                task_que_size_--;

                // 如果非空，通知其它线程也可以干活了
                if (task_que_size_ != 0)
                {
                    not_empty_.notify_all();
                }
                // 通知非满条件
                not_full_.notify_all();
            } // 锁只需要加在操作任务队列，不能执行任务的时候还加锁。切记，锁应该锁住什么区域。

            // 执行任务
            task();
            
            // 执行任务完，空闲线程+1
            this->idle_threads_++;
            last_time = std::chrono::high_resolution_clock().now();
        }
    }

    // 设置线程池模式
    void SetThreadPoolModel(ThreadPoolModel threadpool_model)
    {
        if (is_start_)
        {
            std::cerr << "can`t set thread pool model because the thread pool is start !" << std::endl;
            return;
        }
        this->thread_pool_model = threadpool_model;
    }
    // 设置线程池内线程上限
    void SetThreadPoolThreadMax(size_t max_size)
    {
        if (is_start_)
        {
            std::cerr << "can`t set threads max because the thread pool is start !" << std::endl;
            return;
        }
        this->max_thread_size_ = max_size;
    }
    // 设置任务队列上限
    void SetTaskQueMaxThreashhold(size_t task_que_max)
    {
        if (is_start_)
        {
            std::cerr << "can`t set task que max because the thread pool is start !" << std::endl;
            return;
        }
        this->task_que_max_ = task_que_max;
    }
    // 往任务队列里插入任务
    // 函数模板，可变参数，引用推导，引用折叠
    template <typename Func, typename... Args>
    auto SubmitTask(Func &&func, Args &&...args) -> std::future<decltype(func(args...))>
    {
        // 获取函数返回值
        using Rectype = decltype(func(args...));
        //将任务打包 ,  使用智能指针指向，防止外部任务生命周期结束.打包一定是使用std::bind将函数对象和参数bind在一起
        auto Task = std::make_shared<std::packaged_task<Rectype()>>(
            std::bind(std::forward<Func>(func),std::forward<Args>(args)...));
        //函数返回结果绑定
        std::future<Rectype> result = Task->get_future();

        // 获取锁
        std::unique_lock<std::mutex> lock(this->task_que_mtx_);
        // 检查任务队列能否插入，如果不能等待1s，超时即表示本次插入失败。
        auto status = not_full_.wait_for(lock, std::chrono::seconds(1), [&]() -> bool
                                         { return task_que_.size() < task_que_max_; });
        if (!status)
        {
            std::cerr << "submit task fail, plase wait ." << std::endl;
            // 插入失败，返回函数对象的返回值的空值即可，这里绝对不是返回result.
            auto task =  std::make_shared<std::packaged_task<Rectype()>>(
                []()->Rectype{return Rectype();});
            //上面之所以这样函数对象创建，就是为了这里能获得。
            //还得先执行任务才能获取任务的空返回值不是
            (*task)();
            return task->get_future();
        }
        // 任务队列插入任务
        // 这里就是最经典的了，我不知道一个函数的返回值，但是我设置void作为中间层，我在中间层里是函数的本体。
        task_que_.emplace(
            [Task](){
                    (*Task)();
                });
        //task_que_.emplace(task);
        task_que_size_++;

        // 根据任务数量增减线程池内线程个数
        if (task_que_size_ > idle_threads_ && cur_thread_size_ < max_thread_size_ && thread_pool_model == ThreadPoolModel::MODEL_CACHEED)
        {
            std::cout << "create new thread" << std::endl;
            // 创建线程对象的时候，把线程函数给到新创建的线程对象
            auto ptr = std::make_unique<Thread>(std::bind(&ThreadPool::ThreadFunc, this, std::placeholders::_1));
            int thread_id = ptr->getThreadId();
            // 直接传递ptr,会在容器内调用拷贝此元素，所以这里用所有权转移进容器内。
            this->threads_.emplace(thread_id, std::move(ptr));
            this->threads_[thread_id]->start();
            // 线程参数改变
            this->idle_threads_++;
            this->cur_thread_size_++;
        }

        // 通知任务队列非空
        not_empty_.notify_all();

        // 返回任务的执行结果。
        return result;
    }

    // 开启线程池
    void start(size_t init_threadpool_size)
    {
        // 设置线程池初始线程个数
        this->init_threadpool_size_ = init_threadpool_size;
        this->cur_thread_size_ = init_threadpool_size;

        for (size_t i = 0; i < init_threadpool_size; i++)
        {
            // 创建线程对象的时候，把线程函数给到新创建的线程对象
            auto ptr = std::make_unique<Thread>(std::bind(&ThreadPool::ThreadFunc, this, std::placeholders::_1));
            int thread_id = ptr->getThreadId();
            // 直接传递ptr,会在容器内调用拷贝此元素，所以这里用所有权转移进容器内。
            this->threads_.emplace(thread_id, std::move(ptr));
        }

        for (size_t i = 0; i < init_threadpool_size; i++)
        {
            this->threads_[i]->start();
            this->idle_threads_++;
        }

        is_start_ = true;
    }
};

#endif